import { PositionLine } from '@/components/PositionLine';
import { Groove, Hole, IPlank, PointObj3, Profile } from '@/types';
import {
  Billboard,
  Circle,
  Edges,
  Html,
  Hud,
  Line,
  ScreenSizer,
} from '@react-three/drei';
import { chunk, clone, merge, round } from 'lodash-es';
import { ComponentProps, useEffect, useMemo, useState } from 'react';
import { Box3, Line3, Vector3 } from 'three';
import { Word } from '../Word';
import { BasisArrow } from './BasisArrow';
import { Basis } from './constants';
import { Axis, EditType } from './types';
import {
  BasisValueMap,
  getBasisValueMap,
  getPointInBasis,
} from './utils/basis';
import { movePoints } from './utils/point';

type PositionLineProps = ComponentProps<typeof PositionLine>;

export function EditorCanvasElement(props: {
  plank: {
    width: number;
    height: number;
    thickness: number;
    profile?: Profile;
    hole?: Hole;
    hoveredHole?: Hole;
    groove?: Groove;
    hoveredGroove?: Groove;
    hoveredPlank?: boolean;
  };
  basis?: { x: Basis; y: Basis };
  showBasis?: { x: boolean; y: boolean };
  highlightProfilePoint: number;
  onChangeHole(hole: Hole): void;
  onChangeGroove(groove: Groove): void;
  onChangeProfile(profile: Profile): void;
  onStopEditHole(): void;
  onStopEditGroove(): void;
}) {
  const {
    plank,
    plank: {
      width,
      height,
      hole,
      hoveredHole,
      groove,
      hoveredGroove,
      profile,
      hoveredPlank,
      thickness,
    },
    basis,
    showBasis,
    highlightProfilePoint,
    onChangeHole,
    onChangeGroove,
    onChangeProfile,
    onStopEditHole,
    onStopEditGroove,
  } = props;
  // console.log({basis});

  // console.log(profile.points);

  const [positionPoint, setPositionPoint] = useState<{
    type: EditType;
    point: PointObj3;
  }>();
  useEffect(() => {
    if (!hole) {
      positionPoint?.type === EditType.Hole && setPositionPoint(undefined);
      return;
    }
    setPositionPoint({
      type: EditType.Hole,
      point: hole.start,
    });
  }, [hole]);
  useEffect(() => {
    if (!groove) {
      positionPoint?.type === EditType.Groove && setPositionPoint(undefined);
      return;
    }
    const point = groove.start;
    if (!point) return;
    // console.log('groove', { point });

    setPositionPoint({
      type: EditType.Groove,
      point,
    });
  }, [groove]);

  const basisValueMap = getBasisValueMap({ width, height });
  const profilePoints = useMemo(() => {
    if (!profile) return;
    return getProfilePositionPoints(profile);
  }, [profile]);

  type PointBasisLineProps = ComponentProps<typeof PointBasisLine>;
  const onPointChange: PointBasisLineProps['onChange'] = ({ axis, delta }) => {
    const offset = { [axis]: delta };
    switch (positionPoint!.type) {
      case EditType.Hole:
        movePoints([hole!.start, hole!.end], offset);
        onChangeHole(hole!);
        break;
      case EditType.Groove:
        movePoints([groove!.start!, groove!.end!], offset);
        onChangeGroove(groove!);
        break;
      case EditType.Plank:
        // console.log('before', positionPoint?.point);
        movePoints([positionPoint!.point], offset);
        // console.log('after', positionPoint?.point);

        onChangeProfile(
          merge(profile, {
            points: profilePoints!.map(({ x, y }) => [x, y]).flat(),
          }),
        );
        break;
    }
  };

  const halfThickness = thickness / 2;

  return (
    <>
      <group
        onPointerMissed={(e) => {
          const isInputElement = e.target instanceof HTMLInputElement;
          if (!isInputElement) {
            if (positionPoint?.type === EditType.Hole) {
              onStopEditHole();
            }
            if (positionPoint?.type === EditType.Groove) {
              onStopEditGroove();
            }
            setPositionPoint(undefined);
          }
        }}
      >
        {profilePoints?.map((point, i) => {
          const order = String(i + 1);
          const p = clone(point);
          p.z = p.z + halfThickness;
          return (
            <TipPoint
              highlight={i === highlightProfilePoint}
              label={order}
              point={p}
              // onClick={() =>
              //   setPositionPoint({
              //     type: EditType.Plank,
              //     point,
              //   })
              // }
            />
          );
        })}
      </group>
      {positionPoint?.point && (
        <PointBasisLine
          point={positionPoint.point}
          basis={{ x: Basis.Left, y: Basis.Back }}
          basisValueMap={basisValueMap}
          onChange={onPointChange}
        />
      )}
      {hoveredHole && <HoleSizeTip hole={hoveredHole} />}
      {hoveredGroove && <GrooveSizeTip groove={hoveredGroove} />}
      {basis && (
        <BasisArrow
          show={showBasis}
          width={width}
          height={height}
          basis={basis}
        />
      )}
      {hoveredPlank && (
        <PlankSizeTip width={width} height={height} thickness={thickness} />
      )}
    </>
  );
}

function getProfilePositionPoints(profile: Profile) {
  return chunk(profile.points, 2).map(([x, y]) => ({
    x,
    y,
    z: 0,
  }));
}

function getHolePositionPoint(hole: Hole) {
  return new Line3(
    new Vector3(hole.start.x, hole.start.y, hole.start.z),
    new Vector3(hole.end.x, hole.end.y, hole.end.z),
  ).getCenter(new Vector3());
}
function getGroovePositionPoint(groove: Groove) {
  if (!groove.start || !groove.end) return;
  return new Line3(
    new Vector3(groove.start.x, groove.start.y, groove.start.z),
    new Vector3(groove.end.x, groove.end.y, groove.end.z),
  ).getCenter(new Vector3());
}

export function PointBasisLine(props: {
  point: PointObj3;
  basis: { x: Basis; y: Basis };
  basisValueMap: BasisValueMap;
  onChange?({ axis }: { axis: Axis; delta: number }): void;
}) {
  const { point, basis, basisValueMap, onChange } = props;

  const pointVector3 = new Vector3(point.x, point.y, point.z);
  const { pointInBasisX, pointInBasisY } = getPointInBasis({
    point,
    basis,
    basisValueMap,
  });
  const lineX = new Line3(
    pointVector3,
    new Vector3(pointInBasisX.x, pointInBasisX.y, pointVector3.z),
  );
  const lineY = new Line3(
    pointVector3,
    new Vector3(pointInBasisY.x, pointInBasisY.y, pointVector3.z),
  );
  const onChangePoint = (axis: Axis) => {
    return (({ delta }) => {
      onChange?.({ axis, delta });
    }) as PositionLineProps['onChange'];
  };
  return (
    <>
      <PositionLine line={lineX} onChange={onChangePoint(Axis.y)} />
      <PositionLine line={lineY} onChange={onChangePoint(Axis.x)} />
    </>
  );
}

export function TipPoint(props: {
  highlight?: boolean;
  label: string;
  point: PointObj3;
  size?: number;
  onClick?: () => void;
}) {
  const { highlight, label, point, size, onClick } = props;
  const [hovered, setHover] = useState(false);
  const circleSize = size ?? 16;
  const circleRadius = circleSize / 2;
  const fontSize = circleSize * 0.8;

  return (
    <ScreenSizer position={[point.x, point.y, point.z]}>
      <Billboard>
        <Circle
          onPointerOver={() => setHover(true)}
          onPointerOut={() => setHover(false)}
          onClick={onClick}
          args={[circleRadius]}
        >
          <meshBasicMaterial color={highlight ? '#3E82F7' : 'white'} />
          <Edges scale={1} color="gray" />
        </Circle>
      </Billboard>
      <Word color={highlight ? 'white' : 'gray'} fontSize={fontSize}>
        {label}
      </Word>
    </ScreenSizer>
  );
}

function HoleSizeTip(props: { hole: Hole }) {
  const { hole } = props;
  const { diameter, depth } = hole;
  const line = new Line3(
    new Vector3(hole.start.x, hole.start.y, hole.start.z),
    new Vector3(hole.end.x, hole.end.y, hole.end.z),
  );
  const center = line.getCenter(new Vector3());
  const diameterPosition = center
    .clone()
    .addScaledVector(new Vector3(0, 0, 1), line.distance() / 2);
  const depthPosition = center
    .clone()
    .addScaledVector(new Vector3(0, 1, 0), diameter / 2);
  return (
    <>
      <Html center position={diameterPosition} className="pointer-events-none">
        <div className="relative leading-none">
          <span>Φ{hole.diameter}</span>
          <span className="absolute top-0 right-0 translate-x-full translate-y-full">
            {hole.depth}
          </span>
        </div>
      </Html>
    </>
  );
}
function GrooveSizeTip(props: { groove: Groove }) {
  const { groove } = props;
  if (!groove.start || !groove.end || !groove.width) return;
  const line = new Line3(
    new Vector3(groove.start.x, groove.start.y, groove.start.z),
    new Vector3(groove.end.x, groove.end.y, groove.end.z),
  );
  const center = line.getCenter(new Vector3());
  const grooveLengthPosition = center
    .clone()
    .addScaledVector(new Vector3(0, 1, 0), 10);
  // console.log(depthPosition);
  const grooveLength = round(line.distance(), 2);
  const widthPosition = line.start;
  const depthPosition = line.end;
  const lineDirection = line.delta(new Vector3()).normalize();
  const preLineDirection = new Vector3(0, 0, 1)
    .cross(lineDirection)
    .normalize();
  // console.log(lineDirection, preLineDirection);
  const offset = -5;
  const lineColor = 'black';

  return (
    <>
      <Line
        points={[
          line.start
            .clone()
            .addScaledVector(lineDirection, offset)
            .addScaledVector(preLineDirection, groove.width / 2),
          line.start
            .clone()
            .addScaledVector(lineDirection, offset)
            .addScaledVector(preLineDirection, -groove.width / 2),
        ]}
        color={lineColor}
      />
      <Line
        points={[
          line.start
            .clone()
            .addScaledVector(preLineDirection, offset + -groove.width / 2),
          line.end
            .clone()
            .addScaledVector(preLineDirection, offset + -groove.width / 2),
        ]}
        color={lineColor}
      />

      <Line
        points={[
          line.end
            .clone()
            .addScaledVector(lineDirection, -offset)
            .addScaledVector(new Vector3(0, 0, 1), groove.depth / 2),
          line.end
            .clone()
            .addScaledVector(lineDirection, -offset)
            .addScaledVector(new Vector3(0, 0, 1), -groove.depth / 2),
        ]}
        color={lineColor}
      />
      <Html center position={widthPosition} className="pointer-events-none">
        <span>{groove.width}</span>
      </Html>
      <Html center position={depthPosition} className="pointer-events-none">
        <span>{groove.depth}</span>
      </Html>
      <Html
        center
        position={grooveLengthPosition}
        className="pointer-events-none"
      >
        <span>{grooveLength}</span>
      </Html>
    </>
  );
}
function SizeTip(props: { start: Vector3; end: Vector3 }) {
  const { start, end } = props;
  const line = new Line3(start, end);
  const center = line.getCenter(new Vector3());
  const distance = round(line.distance(), 2);
  return (
    <>
      <Line points={[start, end]}></Line>
      <Html center position={center}>
        <span>{distance}</span>
      </Html>
    </>
  );
}
function PlankSizeTip(props: {
  width: number;
  height: number;
  thickness: number;
}) {
  const { width, height, thickness } = props;
  const halfWidth = width / 2;
  const halfHeight = height / 2;
  const widthTipPosition = new Vector3(0, -halfHeight, 0);
  const heightTipPosition = new Vector3(halfWidth, 0, 0);
  const thicknessTipPosition = new Vector3(halfWidth, halfHeight, 0);
  return (
    <>
      <Html center position={widthTipPosition}>
        <span>{width}</span>
      </Html>
      <Html center position={heightTipPosition}>
        <span>{height}</span>
      </Html>
      <Html center position={thicknessTipPosition}>
        <span>{thickness}</span>
      </Html>
    </>
  );
}
